project(
    'libxml2',
    'c',
    version: '2.13.0',
    license: 'MIT',
    default_options: ['buildtype=debug', 'warning_level=3'],
    meson_version: '>= 0.61',
)

v_array = meson.project_version().split('.')
v_maj = v_array[0]
v_min = v_array[1]
v_mic = v_array[2]
v_nbr = v_maj.to_int() * 10000 + v_min.to_int() * 100 + v_mic.to_int()
v_extra = ''
r = run_command('git', 'describe', check: false)
if (r.returncode() == 0)
    v_extra = '-GIT' + r.stdout().strip()
endif

# install paths
dir_prefix = get_option('prefix')
dir_bin = dir_prefix / get_option('bindir')
dir_include = dir_prefix / get_option('includedir')
dir_pkginclude = dir_include / meson.project_name()
dir_lib = dir_prefix / get_option('libdir')
dir_data = dir_prefix / get_option('datadir')
dir_doc = dir_data / 'doc' / 'libxml2'
dir_locale = dir_prefix / get_option('localedir')

# host

host_os = host_machine.system()

cygwin = 'cygwin'
windows = 'windows'
sys_cygwin = cygwin.contains(host_os)
sys_windows = windows.contains(host_os)

libxml2_cflags = []
xml_cflags = ''
dep_args = []

if sys_cygwin or sys_windows
    if get_option('default_library') == 'static'
        xml_cflags = '-DLIBXML_STATIC'
        libxml2_cflags += '-DLIBXML_STATIC'
        dep_args += '-DLIBXML_STATIC'
    endif
endif

# binaries
cc = meson.get_compiler('c')

# options
want_c14n = get_option('c14n')
want_catalog = get_option('catalog')
want_debug = get_option('debuging')
want_ftp = get_option('ftp')
want_history = get_option('history')
want_html = get_option('html')
want_http = get_option('http')
want_ipv6 = get_option('ipv6')
want_iso8859x = get_option('iso8859x')
want_legacy = get_option('legacy')
want_output = get_option('output')
want_pattern = get_option('pattern')
want_push = get_option('push')
want_python = get_option('python')
want_reader = get_option('reader')
want_readline = get_option('readline')
want_regexps = get_option('regexps')
want_sax1 = get_option('sax1')
want_schemas = get_option('schemas')
want_schematron = get_option('schematron')
want_thread_alloc = get_option('thread-alloc')
want_tls = get_option('tls')
want_tree = get_option('tree')
want_valid = get_option('valid')
want_writer = get_option('writer')
want_xinclude = get_option('xinclude')
want_xpath = get_option('xpath')
want_xptr = get_option('xptr')
want_xptr_locs = get_option('xptr-locs')

# TODO: Options should be three-valued: "yes", "no", default

# TODO: Legacy defaults

# hard dependencies on options

if want_c14n == true
    if want_output == false
        message('-Dc14n=true overrides -Doutput')
    endif
    want_output = true
    if want_xpath == false
        message('-Dc14n=true overrides -Dxpath')
    endif
    want_xpath = true
endif

if want_schemas == true
    if want_pattern == false
        message('-Dschemas=true overrides -Dpattern')
    endif
    want_pattern = true
    if want_regexps == false
        message('-Dschemas=true overrides -Dregexps')
    endif
    want_regexps = true
endif

if want_schematron == true
    if want_pattern == false
        message('-Dschematron=true overrides -Dpattern')
    endif
    want_pattern = true
    if want_tree == false
        message('-Dschematron=true overrides -Dtree')
    endif
    want_tree = true
    if want_xpath == false
        message('-Dschematron=true overrides -Dxpath')
    endif
    want_xpath = true
endif

if want_reader == true
    if want_push == false
        message('-Dreader=true overrides -Dpush')
    endif
    want_push = true
    if want_tree == false
        message('-Dreader=true overrides -Dtree')
    endif
    want_tree = true
endif

if want_writer == true
    if want_output == false
        message('-Dwriter=true overrides -Doutput')
    endif
    want_output = true
    if want_push == false
        message('-Dwriter=true overrides -Dpush')
    endif
    want_push = true
endif

if want_xinclude == true
    if want_xpath == false
        message('-Dxinclude=true overrides -Dxpath')
    endif
    want_xpath = true
endif

if want_xptr_locs == true
    if want_xptr == false
        message('-Dxptr-locs=true overrides -Dxptr')
    endif
    want_xptr = true
endif

if want_xptr == true
    if want_xpath == false
        message('-Dxptr=true overrides -Dxpath')
    endif
    want_xpath = true
endif

# minimum dependencies

if get_option('minimum')
    # TODO: This is should allow other options
    want_c14n = false
    want_catalog = false
    want_debug = false
    want_history = false
    want_html = false
    want_http = false
    want_ipv6 = false
    want_iso8859x = false
    want_output = false
    want_pattern = false
    want_push = false
    want_python = false
    want_reader = false
    want_readline = false
    want_regexps = false
    want_sax1 = false
    want_schemas = false
    want_schematron = false
    want_thread_alloc = false
    want_tree = false
    want_valid = false
    want_writer = false
    want_xinclude = false
    want_xpath = false
    want_xptr = false
    want_xptr_locs = false
else
    # Disable dependent modules
    if want_output == false
        want_c14n = false
        want_writer = false
    endif
    if want_pattern == false
        want_schemas = false
        want_schematron = false
    endif
    if want_push == false
        want_reader = false
        want_writer = false
    endif
    if want_regexps == false
        want_schemas = false
    endif
    if want_tree == false
        want_reader = false
        want_schematron = false
    endif
    if want_xpath == false
        want_c14n = false
        want_schematron = false
        want_xinclude = false
        want_xptr = false
    endif
endif

cflags_try = []

### workaround for native compilers, see configure.ac
if cc.get_argument_syntax() == 'gcc'
    cflags_try += [
        '-Wshadow',
        '-Wpointer-arith',
        '-Wcast-align',
        '-Wwrite-strings',
        '-Wstrict-prototypes',
        '-Wmissing-prototypes',
        '-Wno-long-long',
        '-Wno-format-extra-args',
    ]

    if host_machine.cpu_family() == 'alpha'
        cflags_try += '-mieee'
    endif
else
    if host_machine.cpu_family() == 'alpha'
        cflags_try += '-ieee'
    elif host_machine.cpu_family() == 'parisc'
        cflags_try += '-Wp,-H30000'
    endif
endif

foreach cf : cflags_try
    if cc.has_argument(cf)
        libxml2_cflags += cf
    endif
endforeach

# configuration
#
# X : done
# N : not done
#
# [X] config.h.in
# [X] include/libxml/xmlversion.h.in
# [N] libxml-2.0-uninstalled.pc.in
# [X] libxml-2.0.pc.in
# [X] libxml2-config.cmake.in
# [X] python/setup.py.in
# [N] xml2-config.in

## config.h
config_h = configuration_data()
config_h.set_quoted('PACKAGE_NAME', meson.project_name())
config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
config_h.set_quoted('PACKAGE_BIN_DIR', dir_bin)
config_h.set_quoted('PACKAGE_LIB_DIR', dir_lib)
config_h.set_quoted('PACKAGE_DATA_DIR', dir_data)
config_h.set_quoted('LOCALEDIR', dir_locale)

# header files
xml_check_headers = [
    'stdint.h',
    'fcntl.h',
    'unistd.h',
    'sys/stat.h',
    'sys/mman.h',
    'sys/socket.h',
    'netinet/in.h',
    'arpa/inet.h',
    'netdb.h',
    'sys/select.h',
    'poll.h',
    'sys/time.h',
    'sys/timeb.h',
    'dl.h',
    'dlfcn.h',
    'glob.h',
]

foreach header : xml_check_headers
    if cc.has_header(header)
        config_h.set10('HAVE_' + header.underscorify().to_upper(), true)
    endif
endforeach

# library functions
xml_check_functions = [
    # fct             | header
    ['gettimeofday', 'sys/time.h'],
    ['ftime', 'sys/timeb.h'],
    ['stat', 'sys/stat.h'],
    ['mmap', 'sys/mman.h'],
    ['munmap', 'sys/mman.h'],
]

foreach function : xml_check_functions
    if cc.has_header_symbol(function[1], function[0])
        config_h.set10('HAVE_' + function[0].to_upper(), true)
    endif
endforeach

# library

config_dir = [include_directories('.'), include_directories('include')]

## dependencies

xml_deps = []

### math library
if sys_windows == false
    m_dep = cc.find_library('m', required: false)
    if m_dep.found()
        xml_deps += m_dep
    endif
endif

### thread local storage
support_tls = true
if want_tls == true
    tls_src = '''
#include <threads.h>
int main()
{
    _Thread_local int v;
    return 0;
}
    '''
    res = cc.compiles(tls_src, name: '_Thread_local')
    if res == true
        config_h.set('XML_THREAD_LOCAL', '_Thread_local')
    else
        tls_src = '''
int main()
{
    __thread int v;
    return 0;
}
        '''
        res = cc.compiles(tls_src, name: '__thread')
        if res == true
            config_h.set('XML_THREAD_LOCAL', '__thread')
        else
            tls_src = '''
int main()
{
    __declspec(thread) int v;
    return 0;
}
            '''
            res = cc.compiles(tls_src, name: '__declspec(thread)')
            if res == true
                config_h.set('XML_THREAD_LOCAL', '__declspec(thread)')
            else
                want_tls = false
                support_tls = false
            endif
        endif
    endif
endif

### __attribute__((destructor))
if cc.has_function_attribute('destructor')
    config_h.set10('HAVE_ATTRIBUTE_DESTRUCTOR', true)
    config_h.set('ATTRIBUTE_DESTRUCTOR', '__attribute__((destructor))')
endif

### DSO support
if sys_cygwin == true
    module_extension = '.dll'
elif sys_windows == true
    module_extension = '.dll'
else
    module_extension = '.so'
endif

dl_dep = dependency('', required: false)
if not get_option('minimum')
    if host_machine.system() != 'windows'
        if meson.version().version_compare('>=0.62')
            dl_dep = dependency('dl', required: get_option('modules'))
        else
            dl_dep = cc.find_library('dl', required: get_option('modules'))
        endif
        if dl_dep.found()
            config_h.set10('HAVE_DLOPEN', true)
            xml_deps += dl_dep
        endif
    elif get_option('modules').allowed()
        dl_dep = declare_dependency()
    endif
endif

### threads
threads_dep = dependency('', required: false)
if not get_option('minimum')
    if host_machine.system() != 'windows'
        threads_dep = dependency('threads', required: get_option('threads'))
        if threads_dep.found()
            config_h.set10('HAVE_PTHREAD_H', true)
            xml_deps += threads_dep
        endif
    elif get_option('threads').allowed()
        threads_dep = declare_dependency()
    endif
endif

want_thread_alloc = threads_dep.found()

### xmllint shell history
xmllint_deps = []
if want_history == true and want_readline == true
    termlib_lib = ['ncurses', 'curses', 'termcap', 'terminfo', 'termlib']

    foreach tl : termlib_lib
        termlib_dep = cc.find_library(tl)
        if (
            termlib_dep.found()
            and cc.has_function('tputs', dependencies: termlib_dep)
        )
            xmllint_deps += termlib_dep
            config_h.set10('HAVE_LIB' + tl.underscorify().to_upper(), true)
            break
        endif
    endforeach

    history_dep = dependency('history', required: false)
    if history_dep.found()
        xmllint_deps += history_dep
        config_h.set10('HAVE_LIBHISTORY', true)
    endif

    readline_dep = dependency('readline', required: false)
    if readline_dep.found()
        xmllint_deps += readline_dep
        config_h.set10('HAVE_LIBREADLINE', true)
    endif
endif

### crypto
if sys_windows == true
    bcrypt_dep = cc.find_library('bcrypt', required: true)
    xml_deps += bcrypt_dep
endif

### inet
if want_http == true or want_ftp == true
    if sys_windows == true
        ws2_dep = cc.find_library('ws2_32', required: true)
        xml_deps += ws2_dep
    else
        has_in_libc = cc.has_function('gethostbyname')
        if has_in_libc == false
            nsl_dep = cc.find_library('nsl', required: true)
            if nsl_dep.found()
                has_in_nsl = cc.has_function(
                    'gethostbyname',
                    dependencies: nsl_dep,
                    required: false,
                )
                if has_in_nsl == true
                    xml_deps += nsl_dep
                endif
            endif
        endif
    endif

    ### socket length
    socklen_src = '''
#include <stddef.h>
#ifdef _WIN32
  #include <ws2tcpip.h>
#else
  #include <sys/socket.h>
#endif
int main()
{
    (void)getsockopt (1, 1, 1, NULL, (socklen_t *)NULL);
    return 0;
}
    '''
    res = cc.compiles(socklen_src, name: 'socket length as socklen_t')
    if res == true
        config_h.set('XML_SOCKLEN_T', 'socklen_t')
    else
        socklen_src = '''
#include <stddef.h>
#include <sys/socket.h>
int main()
{
    (void)getsockopt (1, 1, 1, NULL, (size_t *)NULL);
    return 0;
}
        '''
        res = cc.compiles(socklen_src, name: 'socket length as size_t')
        if res == true
            config_h.set('XML_SOCKLEN_T', 'size_t')
        else
            socklen_src = '''
#include <stddef.h>
#include <sys/socket.h>
int main()
{
    (void)getsockopt (1, 1, 1, NULL, (int *)NULL);
    return 0;
}
            '''
            res = cc.compiles(socklen_src, name: 'socket length as int')
            if res == false
                message('could not determine socket length type, use int')
            endif
            config_h.set('XML_SOCKLEN_T', 'int')
        endif
    endif

    if want_ipv6 == true
        ### IPV6 on Windows has been supported since Windows XP SP1 (around 2003)
        ### see:
        ### https://learn.microsoft.com/en-us/windows/win32/winsock/ipv6-support-2
        ### nevertheless, we check it like autotools
        ipv6_src = '''
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#endif
int main()
{
    struct sockaddr_storage ss;
    socket(AF_INET6, SOCK_STREAM, 0);
    getaddrinfo(0, 0, 0, 0);
    return 0;
}
        '''
        res = cc.compiles(ipv6_src, name: 'support for IPV6')
        if res == true
            config_h.set10('SUPPORT_IP6', true)
        endif
    endif
endif

### zlib
if not get_option('minimum')
    zlib_dep = dependency('zlib', required: get_option('zlib'))
else
    zlib_dep = dependency('', required: false)
endif
xml_deps += zlib_dep

### lzma
if not get_option('minimum')
    lzma_dep = dependency('liblzma', required: get_option('lzma'))
else
    lzma_dep = dependency('', required: false)
endif
xml_deps += lzma_dep

### iconv
if not get_option('minimum')
    iconv_dep = dependency('iconv', required: get_option('iconv'))
else
    iconv_dep = dependency('', required: false)
endif
xml_deps += iconv_dep

if not iconv_dep.found() and want_iso8859x == false
    want_iso8859x = false
else
    want_iso8859x = true
endif

# icu
icu_dep = dependency('icu-i18n', method: 'pkg-config', required: get_option('icu'))
if icu_dep.found()
    def_var = icu_dep.get_variable(pkgconfig: 'DEFS')
    config_dir += include_directories(def_var)
    xml_deps += icu_dep
endif

subdir('include/libxml')

# Set config_h after all subdirs and dependencies have set values

configure_file(output: 'config.h', configuration: config_h)

## libxml2 library

xml_src = [
    'buf.c',
    'chvalid.c',
    'dict.c',
    'entities.c',
    'encoding.c',
    'error.c',
    'globals.c',
    'hash.c',
    'list.c',
    'parser.c',
    'parserInternals.c',
    'SAX2.c',
    'threads.c',
    'tree.c',
    'uri.c',
    'valid.c',
    'xmlIO.c',
    'xmlmemory.c',
    'xmlstring.c',
]

xml_opt_src = [
    [want_c14n, ['c14n.c']],
    [want_catalog, ['catalog.c']],
    [want_debug, ['debugXML.c']],
    [want_ftp, ['nanoftp.c']],
    [want_html, ['HTMLparser.c', 'HTMLtree.c']],
    [want_http, ['nanohttp.c']],
    [want_legacy, ['legacy.c']],
    [lzma_dep.found(), ['xzlib.c']],
    [dl_dep.found(), ['xmlmodule.c']],
    [want_output, ['xmlsave.c']],
    [want_pattern, ['pattern.c']],
    [want_reader, ['xmlreader.c']],
    [want_regexps, ['xmlregexp.c', 'xmlunicode.c']],
    [want_sax1, ['SAX.c']],
    [want_schemas, ['relaxng.c', 'xmlschemas.c', 'xmlschemastypes.c']],
    [want_schemas and not want_xpath, ['xpath.c']],
    [want_schematron, ['schematron.c']],
    [want_writer, ['xmlwriter.c']],
    [want_xinclude, ['xinclude.c']],
    [want_xpath, ['xpath.c']],
    [want_xptr, ['xlink.c', 'xpointer.c']],
]

foreach file : xml_opt_src
    want = file[0]
    src = file[1]
    if want == true
        if src.length() > 1
            foreach s : src
                xml_src += s
            endforeach
        else
            xml_src += src
        endif
    endif
endforeach

xml_lib = library(
    'xml2',
    files(xml_src),
    c_args: libxml2_cflags,
    dependencies: xml_deps,
    include_directories: config_dir,
    install: true,
    version: meson.project_version(),
)

dep_inc = include_directories('include')
xml_dep = declare_dependency(include_directories: dep_inc, link_with: xml_lib, compile_args: dep_args)

meson.override_dependency('libxml-2.0', xml_dep)

## xmllint tool

executable(
    'xmllint',
    files('xmllint.c'),
    dependencies: [xml_dep, xmllint_deps],
    include_directories: config_dir,
    install: true,
)

## xmlcatalog tool

executable(
    'xmlcatalog',
    files('xmlcatalog.c'),
    dependencies: [xml_dep, xmllint_deps],
    include_directories: config_dir,
    install: true,
)

## testdso module

testdso_mod = shared_module(
    'testdso',
    files('testdso.c'),
    build_rpath: get_option('libdir'),
    include_directories: config_dir,
    name_prefix: '',
)

## tests

checks = [
    'runsuite',
    'runtest',
    'runxmlconf',
# Disabled for now, see #694
#    'testModule',
    'testThreads',
    'testapi',
    'testchar',
    'testdict',
    'testlimits',
    'testparser',
    'testrecurse',
]

foreach check : checks
    exe = executable(
        check,
        files(check + '.c'),
        dependencies: [threads_dep, xml_dep],
        include_directories: config_dir,
    )
    if check != 'testlimits'
        test(check, exe, timeout: 0, workdir: meson.current_source_dir())
    endif
endforeach

subdir('example')
subdir('doc')

if want_python == true
    subdir('python')
endif

## pc files

pkgmod = import('pkgconfig')

pkgmod.generate(
    xml_lib,
    description: 'libXML library version2.',
    filebase: 'libxml-2.0',
    name: 'libXML',
    variables: 'modules=' + dl_dep.found().to_string('1', '0'),
)

## libxml2-config.cmake file

config_cmake = configuration_data()
config_cmake.set('LIBXML_MAJOR_VERSION', v_maj)
config_cmake.set('LIBXML_MINOR_VERSION', v_min)
config_cmake.set('LIBXML_MICRO_VERSION', v_mic)
config_cmake.set('VERSION', meson.project_version())
config_cmake.set('WITH_ICONV', iconv_dep.found().to_int().to_string())
config_cmake.set('WITH_ICU', icu_dep.found().to_int().to_string())
config_cmake.set('WITH_LZMA', lzma_dep.found().to_int().to_string())
config_cmake.set('WITH_MODULES', dl_dep.found().to_int().to_string())
config_cmake.set('WITH_THREADS', threads_dep.found().to_int().to_string())
config_cmake.set('WITH_ZLIB', zlib_dep.found().to_int().to_string())
config_cmake.set('XML_CFLAGS', xml_cflags)
configure_file(
    input: 'libxml2-config.cmake.in',
    output: 'libxml2-config.cmake',
    configuration: config_cmake,
    install_dir: dir_lib / 'cmake' / 'libxml2',
)

install_data(files('libxml.m4'), install_dir: dir_data / 'aclocal')

if support_tls == false
    message('===============================================================')
    message('WARNING: Your C compiler appears to not support thread-local')
    message('storage. Future versions of libxml2 will require this feature')
    message('for multi-threading.')
    message('===============================================================\n',
    )
endif

# summary

summary(
    {
        'OS': host_os,
        'c14n': want_c14n,
        'catalog': want_catalog,
        'debug': want_debug,
        'ftp': want_ftp,
        'history': want_history,
        'html': want_html,
        'http': want_http,
        'iconv': iconv_dep.found(),
        'icu': icu_dep.found(),
        'ipv6': want_ipv6,
        'iso8859x': want_iso8859x,
        'legacy': want_legacy,
        'lzma': lzma_dep.found(),
        'modules': dl_dep.found(),
        'output': want_output,
        'pattern': want_pattern,
        'push': want_push,
        'python': want_python,
        'reader': want_reader,
        'readline': want_readline,
        'regexps': want_regexps,
        'sax1': want_sax1,
        'schemas': want_schemas,
        'schematron': want_schematron,
        'threads': threads_dep.found(),
        'thread-alloc': want_thread_alloc,
        'tls': want_tls,
        'tree': want_tree,
        'valid': want_valid,
        'writer': want_writer,
        'xinclude': want_xinclude,
        'xpath': want_xpath,
        'xptr': want_xptr,
        'xptr-locs': want_xptr_locs,
        'zlib': zlib_dep.found(),
    },
    section: 'Configuration Options Summary:',
)
